package utils

import (
	"context"
	"errors"
	"regexp"
	"strconv"
	"strings"

	"github.com/go-redis/redis/v8"
	"github.com/google/uuid"
)

var _ RedisMemTestCase = (*redisMemTestCase)(nil)

type RedisMemTestCase interface {
	WriteToRedis(ctx context.Context, size, count int) error
	CalcMemoryUsagePerKey(ctx context.Context) (float64, error)
	FetchUsedMemory(ctx context.Context) (int64, error)
	Reset(ctx context.Context) error
	Shutdown() error
}

type redisMemTestCase struct {
	client        *redis.Client
	size          int
	count         int
	usedMemReg    *regexp.Regexp
	usedMemBefore int64
}

func NewRedisMemTestCase(opts *redis.Options) RedisMemTestCase {
	return &redisMemTestCase{
		client:        redis.NewClient(opts),
		size:          0,
		count:         0,
		usedMemReg:    regexp.MustCompile(`\s*used_memory:\s*([0-9]+)\s*`),
		usedMemBefore: 0,
	}
}

func (rmtc *redisMemTestCase) WriteToRedis(ctx context.Context, size int, count int) error {
	var err error
	rmtc.usedMemBefore, err = rmtc.FetchUsedMemory(ctx)
	if err != nil {
		return err
	}
	rmtc.size = size
	rmtc.count = count
	val := strings.Repeat("x", size)
	for idx := 0; idx < count; idx++ {
		err = rmtc.client.Set(ctx, uuid.NewString(), val, 0).Err()
		if err != nil {
			return err
		}
	}

	return nil
}

func (rmtc *redisMemTestCase) CalcMemoryUsagePerKey(ctx context.Context) (float64, error) {
	usedMemAfter, err := rmtc.FetchUsedMemory(ctx)
	if err != nil {
		return 0.0, err
	}

	return (float64(usedMemAfter) - float64(rmtc.usedMemBefore)) / float64(rmtc.count), nil
}

func (rmtc *redisMemTestCase) FetchUsedMemory(ctx context.Context) (int64, error) {
	res, err := rmtc.client.Info(ctx, "memory").Result()
	if err != nil {
		return 0, err
	}
	var used_memory int64 = 0
	if rmtc.usedMemReg.MatchString(res) {
		subs := rmtc.usedMemReg.FindStringSubmatch(res)
		used_memory, err = strconv.ParseInt(subs[1], 10, 64)
		if err != nil {
			return 0, err
		}
	} else {
		return 0, errors.New("info memory result regexp not match")
	}

	return used_memory, nil
}

func (rmtc *redisMemTestCase) Reset(ctx context.Context) error {
	rmtc.size = 0
	rmtc.count = 0
	rmtc.usedMemBefore = 0

	return rmtc.client.FlushDB(ctx).Err()
}

func (rmtc *redisMemTestCase) Shutdown() error {
	return rmtc.client.Close()
}
